在現代 Vue 應用中,狀態管理是一個非常重要的部分,尤其是當應用變得越來越複雜時,如何有效地管理全局狀態變得至關重要。Pinia 作為 Vue 的新一代狀態管理工具,簡潔、靈活且易於與 TypeScript 結合。在這篇文章中,我們將介紹 Pinia 的基本用法,並展示如何在 Vue 中高效地管理應用狀態。
Pinia 是 Vue 3 官方推薦的狀態管理庫,它是 Vuex 的替代方案,設計更加簡單和現代化。Pinia 不僅能夠滿足大型應用的狀態管理需求,還具有與 Vue 3 的 Composition API 深度集成的特性,支持 TypeScript、插件擴展、模組化等功能。
首先,我們需要在 Vue 項目中安裝 Pinia。
bun add pinia
安裝完成後,我們需要在應用中引入並初始化 Pinia。(檔案: src/main.ts
)
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';
const app = createApp(App);
app.use(createPinia());
app.mount('#app');
這段代碼將 Pinia 集成到 Vue 應用中,使得我們可以在全局範圍內使用 Pinia 來管理狀態。
Pinia 中的 store 是狀態的容器,類似於 Vuex 中的模組。我們將創建一個簡單的計數器 store 來展示 Pinia 的基本功能。這裡我們創建一個檔案 src/stores/useCounterStore.ts
備註:關於 pinia stores 的命名個人建議使用 use...Store
作為命名規則,如專案隨時間變大時,這樣比較不會和 composables
搞混,並且可以快速查找該檔案。
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
},
});
在這個範例中,我們定義了一個名為 counter
的 store,裡面包含 count
狀態以及兩個修改狀態的 action:increment
和 decrement
。
接下來,我們將在 Vue 組件中使用剛剛創建的 Pinia store。
<template>
<div>
<p>當前計數:{{ counterStore.count }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">減少</button>
</div>
</template>
<script lang="ts" setup>
import { useCounterStore } from '../stores/counter';
const counterStore = useCounterStore();
</script>
在這個範例中,我們引入了 useCounterStore
,並在組件的 setup
函數中調用它。這使得我們可以在模板中直接訪問和操作 store 中的狀態和 action。
Pinia 也支持使用 getters 來計算衍生狀態。我們可以在 store 中定義 getter,類似於 Vue 的 computed
。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
},
});
接著,我們可以在組件中使用 doubleCount
這個 getter:
<template>
<div>
<p>當前計數:{{ counterStore.count }}</p>
<p>雙倍計數:{{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">減少</button>
</div>
</template>
<script lang="ts" setup>
import { useCounterStore } from '../stores/counter';
const counterStore = useCounterStore();
</script>
這樣,我們不僅能夠顯示當前計數,還可以顯示其雙倍值,並且一旦 count
發生變化,doubleCount
也會自動更新。
Pinia 與 TypeScript 完美兼容,我們可以充分利用 TypeScript 的型別推斷來增強開發體驗。在前面的範例中,Pinia 已經自動推斷了狀態和 getters 的型別,我們也可以進一步手動定義型別來提高代碼的可讀性和可維護性。
import { defineStore } from 'pinia';
interface CounterState {
count: number;
}
export const useCounterStore = defineStore<'counter', CounterState>('counter', {
state: (): CounterState => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
},
});
在這裡,我們定義了一個 CounterState
介面來明確描述狀態的型別。這樣做的好處是,我們的代碼變得更具可維護性,並且在開發過程中能享受 TypeScript 的型別檢查功能。
(可以整合前面的 zod infer 型別...)
當應用變得越來越大時,我們可以將不同的狀態邏輯拆分到多個 store 中。每個 store 可以像模塊一樣進行管理,這有助於保持代碼清晰並降低維護成本。
這裡我們建立一個使用者模組 src/stores/useUserStore.ts
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
name: 'John Doe',
isLoggedIn: false,
}),
actions: {
login() {
this.isLoggedIn = true;
},
logout() {
this.isLoggedIn = false;
},
},
});
這樣,我們可以將用戶相關的邏輯與計數器分開,並在不同的組件中使用對應的 store 來管理各自的狀態。
有些熟悉 pinia 的讀者,可能會認為 pinia 上述的寫法是屬於比較舊的寫法,他們已經習慣寫 composition-api 的寫法,以下我們改成 composition api 的方式
檔案位置 :src/stores/useCounterStore.ts
,useUserStore 的部分 讀者也可以自行嘗試修改看看。
import { shallowRef, computed } from "vue"
import { defineStore, acceptHMRUpdate } from "pinia";
export const useCounterStore = defineStore("useCounterStore", () => {
// state::
const count = shallowRef<number>(0);
// getter::
const doubleCount = computed<number>(() => {
return count.value * 2;
});
// methods::
const increment = (): void => {
count.value++;
};
const decrement = (): void => {
count.value--;
};
return {
// state::
count,
// getter::
doubleCount,
// methods::
increment,
decrement,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot));
}
關於, pinia store 有兩種寫法,這兩種有哪些優劣我們簡單分析,
傳統的 pinia 寫法(Option store):因為 state
, getters
, actions
分層明確,所以在維護上可以立即找到相應的位置,但在使用上相對就不會那麼彈性,且有更嚴謹的規範結構。
composition-api pinia 寫法(setup store):寫法比較彈性,但也因為非常彈性,所以開發者會比較沒有規範,對於初學者會把狀態和 method 混再一起寫,所以個人建議每一個部分分層下註解在團隊內規範會比較好。
當然兩種寫法有各自的優劣,往後的文章會講述這兩者的進階應用。
Pinia 是 Vue 3 的現代化狀態管理解決方案,它具有簡單、靈活的 API,同時與 Vue 的 Composition API 和 TypeScript 無縫集成。通過本文,我們學習了如何安裝和配置 Pinia、創建和使用 store、定義 getters、使用 TypeScript 增強開發體驗,以及模組化應用中的狀態管理。
接下來的文章中,我們將進一步探討如何在更複雜的應用場景中運用 Pinia,提升狀態管理的靈活性與性能。